/*
 * Copyright 2022 André Claßen
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.burnoutcrew.reorderable

import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.VectorConverter
import androidx.compose.animation.core.VisibilityThreshold
import androidx.compose.animation.core.spring
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.geometry.Offset

/**
 * 拖拽取消动画接口
 * 定义了拖拽取消时的动画行为规范
 */
interface DragCancelledAnimation {
    /**
     * 当拖拽被取消时调用此方法执行动画
     * @param position 被拖拽项目的当前位置
     * @param offset 拖拽偏移量
     */
    suspend fun dragCancelled(position: ItemPosition, offset: Offset)

    /**
     * 当前动画中的项目位置
     */
    val position: ItemPosition?

    /**
     * 当前动画中的偏移量
     */
    val offset: Offset
}

/**
 * 无拖拽取消动画实现
 * 直接忽略拖拽取消事件，不执行任何动画效果
 */
class NoDragCancelledAnimation : DragCancelledAnimation {
    /**
     * 空实现，不执行任何操作
     */
    override suspend fun dragCancelled(position: ItemPosition, offset: Offset) {}

    /**
     * 始终返回null，表示没有正在进行动画的项目
     */
    override val position: ItemPosition? = null

    /**
     * 始终返回零偏移量
     */
    override val offset: Offset = Offset.Zero
}

/**
 * 基于弹簧动画的拖拽取消动画实现
 * 使用Spring动画将被拖拽项目平滑移动回原始位置
 * @param stiffness 弹簧的刚度系数，默认为中低刚度
 */
class SpringDragCancelledAnimation(private val stiffness: Float = Spring.StiffnessMediumLow) : DragCancelledAnimation {
    /**
     * 可动画数值，用于跟踪当前偏移位置
     */
    private val animatable = Animatable(Offset.Zero, Offset.VectorConverter)

    /**
     * 获取当前动画值作为偏移量
     */
    override val offset: Offset
        get() = animatable.value

    /**
     * 当前正在执行动画的项目位置状态
     */
    override var position by mutableStateOf<ItemPosition?>(null)
        private set

    /**
     * 执行拖拽取消动画
     * 将项目从当前偏移位置使用弹簧动画移回原位
     * @param position 被拖拽项目的当前位置
     * @param offset 拖拽偏移量
     */
    override suspend fun dragCancelled(position: ItemPosition, offset: Offset) {
        // 设置当前动画项目位置
        this.position = position
        // 瞬间跳转到指定偏移位置开始动画
        animatable.snapTo(offset)
        // 执行弹簧动画回到原点
        animatable.animateTo(
            Offset.Zero,
            spring(stiffness = stiffness, visibilityThreshold = Offset.VisibilityThreshold)
        )
        // 动画完成后清空位置状态
        this.position = null
    }
}
